One of the difficulties in programming is figuring out how to stich together disparate bits of information. For example you might be trying to serialize a CMap containing pointers to CObject derived classes indexed by integers. In order to do this you need information about the CMap class, serializing objects, serializing collections, and it turns out you also need some information about templated functions and Explicit Specialization. Let's start by looking at CMap.
CMap is confusing because it has four type parameters but two of them seem like redefinitions of the other two. KEY and ARG_KEY are used to control the key part of the map while VALUE and ARG_VALUE are used to control the value part of the map. The ARG_ in the type parameters stands for argument and are used by some of the CMap methods. In my case I used int as both KEY parameters and a pointer to a CObject derived class as both VALUE parameters and that seemed to work. That leads to a declaration that looks like this.
CMap<int, int, CSomeThing*, CSomeThing*> m_someThingMap;
Now the first part to serializing this CMap is to make CSomeThing serializable. The main thing is to override the Serialize() method on the CSomeThing class and add the member values you which to store or load.
void CSomeThing::Serialize(CArchive& ar)
The << operator is used when storing, determined by calling IsStoring() on the CArchive object and the >> operator is used to load values. Generally the Storing and loading parts should be in the same order. Next you need to add the DECLARE_SERIAL macro in the header and the IMPLEMENT_SERIAL macro in the implementation file.
DECLARE_SERIAL(CSomeThing)
IMPLEMENT_SERIAL(CSomeThing, CObject, 1)
These macros give the ability to dynamically create instances of this class and the ability to use the << and >> operators with CArchive.
The next step is to create a specialization of the templated SerializeElements() for your class. This function is called by the CMap serialize method to, as the name suggests, serialize elements. By creating a specialization you can control how elements of your class are serialized. The default is to just save the raw bytes of the object which doesn't work for pointers. The trouble with SerializeElements() is where to put it. If you are like me and haven't had that much experience with templated functions you might try and put it in the SomeThing header file, after all don't you normally put templated functions in the header? If you do that you might get a lot of confusing errors like "void __cdecl SerializeElements<class CSomeThing>(class CArchive &,class CSomeThing *,__int64)" (??$SerializeElements@VCSomeThing@@@@YAXAEAVCArchive@@PEAVCSomeThing@@_J@Z) already defined in SomeThing.obj". As far as I can tell this is caused by the header being included multiple times since it has to be included anytime the doc header is included.
Since this is a specialization of a templated function and not itself a templated function we don't have to put it in the header. The correct place to put it seems to be in the Something implementation file. You do need to define it in the header file by adding the prototype though. This way there's only one specialization but all the classes which reference the header file know there is one and can use it. Now the next question is what to put in the SerializeElements() method. If you look at the example online you might come up with something like this.
template <> void AFXAPI SerializeElements <CSomeThing>(CArchive& ar,
CSomeThing* pNewSomeThings, INT_PTR nCount)
for (int i = 0; i < nCount; i++, pNewSomeThings++)
// Serialize each CSomeThing object
pNewSomeThings->Serialize(ar);
This looks good but if you look at the example array you will notice that it's an array of objects and not an array of pointers. The example is similarly for serializing object elements and not pointers. The main problem is that loading pointer elements requires creating elements. Object elements will get created when the container is created. To handle this we need to use the stream operators instead of the serialize method directly. These operators know how to load and store pointer to objects. This leads us to a SerializeElements() method that looks like this.
template <> void AFXAPI SerializeElements <CSomeThing*>(CArchive& ar,
CSomeThing** pNewSomeThings, INT_PTR nCount)
for (int i = 0; i < nCount; i++, pNewSomeThings++)
Note the type parameter has changed to a CSometThing pointer and that the pNewSomeThing pointer is now a pointer to a pointer. We have to dereference that pointer when using the stream operators. Make sure to update the header definition as well and now all you have to do is serialize the CMap which is just a case of calling its serialize() method.
void CTabbedInfoDoc::Serialize(CArchive& ar)
m_someThingMap.Serialize(ar);
And that should be it.